/**
 * @author Tobias Boehm
 *
 * 06.05.2011
 */
package org.anddev.andengine.extension.tbsengine.server;

import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.UUID;

import org.anddev.andengine.extension.multiplayer.protocol.adt.message.IMessage;
import org.anddev.andengine.extension.multiplayer.protocol.adt.message.client.ClientMessage;
import org.anddev.andengine.extension.multiplayer.protocol.adt.message.client.IClientMessage;
import org.anddev.andengine.extension.multiplayer.protocol.adt.message.server.ServerMessage;
import org.anddev.andengine.extension.multiplayer.protocol.server.BluetoothSocketServer;
import org.anddev.andengine.extension.multiplayer.protocol.server.IClientMessageHandler;
import org.anddev.andengine.extension.multiplayer.protocol.server.SocketServer;
import org.anddev.andengine.extension.multiplayer.protocol.server.SocketServerDiscoveryServer;
import org.anddev.andengine.extension.multiplayer.protocol.server.connector.BluetoothSocketConnectionClientConnector;
import org.anddev.andengine.extension.multiplayer.protocol.server.connector.BluetoothSocketConnectionClientConnector.IBluetoothSocketConnectionClientConnectorListener;
import org.anddev.andengine.extension.multiplayer.protocol.server.connector.ClientConnector;
import org.anddev.andengine.extension.multiplayer.protocol.server.connector.SocketConnectionClientConnector;
import org.anddev.andengine.extension.multiplayer.protocol.server.connector.SocketConnectionClientConnector.ISocketConnectionClientConnectorListener;
import org.anddev.andengine.extension.multiplayer.protocol.shared.BluetoothSocketConnection;
import org.anddev.andengine.extension.multiplayer.protocol.shared.Connection;
import org.anddev.andengine.extension.multiplayer.protocol.shared.IDiscoveryData;
import org.anddev.andengine.extension.multiplayer.protocol.shared.SocketConnection;
import org.anddev.andengine.extension.multiplayer.protocol.util.MessagePool;
import org.anddev.andengine.extension.tbsengine.map.AbsMapOverview;
import org.anddev.andengine.extension.tbsengine.map.AbsMapOverviewLoader;
import org.anddev.andengine.extension.tbsengine.match.AbsServerMatch;
import org.anddev.andengine.extension.tbsengine.match.AbsServerMatchPreparation;
import org.anddev.andengine.extension.tbsengine.match.AbsServerMatchPreparation.MatchPreparationState;
import org.anddev.andengine.extension.tbsengine.match.Turn;
import org.anddev.andengine.extension.tbsengine.message.client.AbsClientMatchMessage;
import org.anddev.andengine.extension.tbsengine.message.client.ClientChatmessageMessage;
import org.anddev.andengine.extension.tbsengine.message.client.ClientJoinOrLeaveServerRequestMessage;
import org.anddev.andengine.extension.tbsengine.message.client.ClientMatchJoinLeaveRequestMessage;
import org.anddev.andengine.extension.tbsengine.message.client.ClientMatchListRequestMessage;
import org.anddev.andengine.extension.tbsengine.message.client.DefaultTBSclientMessageFlags;
import org.anddev.andengine.extension.tbsengine.message.client.ingame.ClientStepVerificationRequestMessage;
import org.anddev.andengine.extension.tbsengine.message.client.ingame.ClientTurnVerificationRequestMessage;
import org.anddev.andengine.extension.tbsengine.message.client.preparematch.AbsClientSlotChangeRequestMessage;
import org.anddev.andengine.extension.tbsengine.message.client.preparematch.ClientPrepareMatchRequestMessage;
import org.anddev.andengine.extension.tbsengine.message.client.preparematch.ClientSlotChangeFactionRequestMessage;
import org.anddev.andengine.extension.tbsengine.message.client.preparematch.ClientSlotChangePlayerRequestMessage;
import org.anddev.andengine.extension.tbsengine.message.client.preparematch.ClientSlotChangeTeamReuqestMessage;
import org.anddev.andengine.extension.tbsengine.message.client.preparematch.ClientSlotChangeToOpenOrClosedRequestMessage;
import org.anddev.andengine.extension.tbsengine.message.client.preparematch.ClientStartMatchRequestMessage;
import org.anddev.andengine.extension.tbsengine.message.client.preparematch.ClientToggleReadyToPlayMessage;
import org.anddev.andengine.extension.tbsengine.message.server.DefaultTBSserverMessageFlags;
import org.anddev.andengine.extension.tbsengine.message.server.ServerChatMessageForwardMessage;
import org.anddev.andengine.extension.tbsengine.message.server.ServerJoinOrLeaveServerReplyMessage;
import org.anddev.andengine.extension.tbsengine.message.server.ServerJoinOrLeaveServerReplyMessage.Reason;
import org.anddev.andengine.extension.tbsengine.message.server.ServerMatchPlayerJoinedLeftAnnouncementMessage;
import org.anddev.andengine.extension.tbsengine.message.server.ingame.AbsServerMatchReconnectRequestReplyMessage;
import org.anddev.andengine.extension.tbsengine.message.server.ingame.ServerMatchIngameVerificationRequestReplyMessage;
import org.anddev.andengine.extension.tbsengine.message.server.ingame.ServerMatchRoundAnnouncementMessage;
import org.anddev.andengine.extension.tbsengine.message.server.ingame.ServerMatchStateChangedAnnouncementMessage;
import org.anddev.andengine.extension.tbsengine.message.server.ingame.ServerMatchStepAnnouncement;
import org.anddev.andengine.extension.tbsengine.message.server.ingame.ServerMatchTurnAnnouncement;
import org.anddev.andengine.extension.tbsengine.message.server.preparematch.AbsServerMatchpreparationJoinRequestReplyMessage;
import org.anddev.andengine.extension.tbsengine.message.server.preparematch.ServerMatchpreparationPromotionMessage;
import org.anddev.andengine.extension.tbsengine.message.server.preparematch.ServerMatchpreparationSlotChangeRequestReplyMessage;
import org.anddev.andengine.extension.tbsengine.message.server.preparematch.ServerMatchpreprataionStartRequestReplyMessage;
import org.anddev.andengine.extension.tbsengine.message.server.preparematch.ServerMatchpreprataionStateChangedAnnouncement;
import org.anddev.andengine.extension.tbsengine.message.server.preparematch.ServerPrepareMatchRequestReplyMessage;
import org.anddev.andengine.extension.tbsengine.player.BaseHumanPlayer;
import org.anddev.andengine.extension.tbsengine.player.BaseServerHumanPlayer;
import org.anddev.andengine.util.path.Path.Step;
import org.xml.sax.SAXException;

import android.app.Application;
import android.content.Context;

/**
 * This is an abstract representation of a game server. It handles client 
 * logins/logouts, the creation and handling of new matches, forwarding of chat
 * messages etcetera.
 * <p>
 * Notice:<br>
 * You have to choose carefully where to place an instance of the GameServer. You 
 * may put it inside an implementation of {@link AbsGameServerService} or inside
 * an {@link Application}.
 * No matter where you put the game server you have to make sure that it doesn't
 * crash if the application goes to the background. Please look at the example.
 * <p>
 * There will be additional functionality for setting up a bluetooth game server 
 * in a later implementation.  
 * 
 * 
 * @author Tobias Boehm
 *
 * @param <MapOverviewClass> - Your implementation of {@link AbsMapOverview}
 * @param <MatchPreparationClass> - Your implementation of {@link AbsServerMatchPreparation}
 * @param <ServerMatchClass> - Your implementation of {@link AbsServerMatch}
 */
public abstract class AbsGameServer<MapOverviewClass extends AbsMapOverview,
									MatchPreparationClass extends AbsServerMatchPreparation,
									ServerMatchClass extends AbsServerMatch> implements DefaultTBSserverMessageFlags, DefaultTBSclientMessageFlags{

	/**
	 * A list of possible game server states. It will be extended in the future.
	 * 
	 * @author Tobias Boehm
	 *
	 */
	public static enum GameServerState{DOWN,RUNNING};
	
	/**
	 * A reference to an {@link SocketServer} which won't be null if you choose
	 * to create an IP socket game server.
	 * 
	 * @see #AbsGameServer(Context, SocketServer)
	 */
	protected SocketServer<SocketConnectionClientConnector> mSocketServer;
	
	/**
	 * A reference to an Implementation of {@link SocketServerDiscoveryServer}.
	 * If this is not null it will start/terminate according to the game server.
	 */
	protected SocketServerDiscoveryServer<IDiscoveryData> mSocketDiscoveryServer;
	
	/**
	 * A reference to an {@link BluetoothSocketServer} which won't be null if 
	 * you choose to create an bluetooth socket game server.
	 * 
	 * @see #AbsGameServer(Context, BluetoothSocketServer)
	 */
	protected BluetoothSocketServer<BluetoothSocketConnectionClientConnector> mBluetoothSocketServer;
	
	/**
	 * A map of flags and corresponding client message classes. This map will be
	 * used to register listeners for incoming client messages. You can add
	 * additional flag <-> class pairs by implementing 
	 * {@link #getAdditionalFlagsClientMessages()}.
	 *  
	 * @see #mFlagsClientMessageHandlersMap
	 */
	protected HashMap<Short,Class<? extends IClientMessage>> mFlagsClientMessageClassesMap;
	
	/**
	 * A map of flags which are identifier for message types and the 
	 * corresponding message handlers to handle those messages. You can add 
	 * additional flag <-> handler pairs by implementing
	 * {@link #createAdditionalFlagsClientMessageHandlers()}.
	 *  
	 * @see #mFlagsClientMessageClassesMap
	 */
	protected HashMap<Short,IClientMessageHandler> mFlagsClientMessageHandlersMap;
	
	/**
	 * This is a server message pool. You need this pool whenever you want to 
	 * send a {@link ServerMessage} to a client. You can register costume 
	 * message types by implementing
	 * {@link #registerAdditionalServerMessages(MessagePool)}.
	 */
	protected MessagePool<IMessage> mServerMessagePool;
	
	/**
	 * This is a flag to show if this is a IP socket server or bluetooth socket
	 * server.
	 */
	protected final boolean mIsBluetoothServer;
	
	/**
	 * This is a flag to represent the game server state.
	 * 
	 * @see GameServerState
	 */
	protected GameServerState mGameServerState = GameServerState.DOWN;
	
	/**
	 * This map contains all playerIDs -> {@link BaseServerHumanPlayer}s which 
	 * are currently logged in into the game server.
	 */
	protected Map<UUID,BaseServerHumanPlayer> mPlayerIDsPlayerMap = Collections.synchronizedMap(new HashMap<UUID,BaseServerHumanPlayer>());
	
	/**
	 * This map contains all matchIDs -> match preparation objects which are
	 * currently active.
	 */
	protected Map<UUID, MatchPreparationClass> mMatchIDsMatchpreparationsMap = Collections.synchronizedMap(new HashMap<UUID, MatchPreparationClass>());
	
	/**
	 * This map contains all matchIDs -> match objects which are currently
	 * running.
	 */
	protected Map<UUID, ServerMatchClass> mMatchIDsMatchMap = Collections.synchronizedMap(new HashMap<UUID, ServerMatchClass>());
		
	protected final Context mContext;
	
	
	/**
	 * This constructor creates an IP socket game server.
	 * 
	 * @param pContext
	 * @param pSocketServer
	 */
	public AbsGameServer(final Context pContext, final SocketServer<SocketConnectionClientConnector> pSocketServer){
		
		this(pContext, pSocketServer, null);
	}
	
	/**
	 * This constructor creates an IP socket game server with the ability to
	 * implement auto server discovery.
	 * 
	 * @param pContext
	 * @param pSocketServer
	 * @param pSocketDiscoveryServer - A socket discovery server for local Wifi
	 * 								   server auto-detection.
	 */
	public AbsGameServer(final Context pContext, final SocketServer<SocketConnectionClientConnector> pSocketServer, final SocketServerDiscoveryServer<IDiscoveryData> pSocketDiscoveryServer){
		
		this.mContext = pContext;
		
		pSocketServer.setClientConnectorListener(new SocketConnectionClienConnectorListener());
		
		this.mSocketServer = pSocketServer;
		
		this.mIsBluetoothServer = false;
		
		this.mSocketDiscoveryServer = pSocketDiscoveryServer;
		
		this.initMapsAndServerMessagePool();
	}
	
	/**
	 * This constructor creates an Bluetooth socket game server.
	 * <p>
	 * Warning:<br>
	 * This is only an inoperative stub. There will be a fully functional
	 * Bluetooth in a future implementation.
	 * 
	 * @param pContext
	 * @param pBluetoothServer
	 */
	public AbsGameServer(final Context pContext, final BluetoothSocketServer<BluetoothSocketConnectionClientConnector> pBluetoothServer){
		
		this.mContext = pContext;
		
		pBluetoothServer.setClientConnectorListener(new BluetoothSocketConnectionClientConnectorListener());
		
		this.mBluetoothSocketServer = pBluetoothServer;
		
		this.mIsBluetoothServer = true;
		
		this.initMapsAndServerMessagePool();
	}
	
	/**
	 * This method encapsulates the initialization of 
	 * {@link #mFlagsClientMessageClassesMap},
	 * {@link #mFlagsClientMessageHandlersMap}
	 * and {@link #mServerMessagePool}. <br>
	 * It also calls your implementations of
	 * {@link #getAdditionalFlagsClientMessages()},
	 * {@link #createAdditionalFlagsClientMessageHandlers()}
	 * and {@link #registerAdditionalServerMessages(MessagePool)}.
	 */
	protected void initMapsAndServerMessagePool(){
		
		this.mFlagsClientMessageClassesMap = this.getFalgsClientMessagesMap();
		
		final Map<Short,Class<? extends IClientMessage>> additionalFlagsClientMessages = this.getAdditionalFlagsClientMessages();
		
		if(additionalFlagsClientMessages != null){
			this.mFlagsClientMessageClassesMap.putAll(additionalFlagsClientMessages);
		}
			
		this.mFlagsClientMessageHandlersMap = this.createClientMessagesFlagsHandlerMap();
		
		final Map<Short, IClientMessageHandler> additionalFlagsClientMessageHandlers = this.createAdditionalFlagsClientMessageHandlers();
		
		if(additionalFlagsClientMessageHandlers != null){
			this.mFlagsClientMessageHandlersMap.putAll(additionalFlagsClientMessageHandlers);
		}
		
		this.mServerMessagePool = this.createServerMessagePool();
		
		this.registerAdditionalServerMessages(this.mServerMessagePool);
	}
	
	/**
	 * This method should return an instance of your implementation of
	 * {@link AbsMapOverview}. It provides the ID of the wanted map.<br>
	 * You may use your implementation of {@link AbsMapOverviewLoader} for this
	 * purpose.
	 *  
	 * @param pChosenMapID - The ID of the wanted map.
	 * 
	 * @return an instance of your implementation of {@link AbsMapOverview}
	 * 
	 * @throws IOException if there is a problem with reading the map file.
	 * @throws SAXException
	 */
	protected abstract MapOverviewClass loadChosenMapOverview(final String pChosenMapID) throws IOException, SAXException;
			
	/**
	 * This method should handle an incoming {@link ClientMatchListRequestMessage}.
	 * <p>
	 * Manual:<br>
	 * At first you may check if the {@link #isPlayerSignedIn(UUID)}. Then you 
	 * may send a {@link ServerMatchpreparationPromotionMessage} or a costume
	 * message for every active match preparation instance to the player that is
	 * in {@link MatchPreparationState#PREPARING} mode and has at least one open
	 * slot. 
	 * <p>
	 * Notice:<br>
	 * You have to use {@link #mServerMessagePool} to obtain and recycle a new
	 * server messages.
	 * You have to use the {@link ClientConnector} inside the corresponding 
	 * {@link BaseHumanPlayer}object to send the messages.
	 *  
	 * @see #mMatchIDsMatchpreparationsMap
	 * @see #mPlayerIDsPlayerMap
	 * 
	 * @param pMessage - An incoming {@link ClientMatchListRequestMessage}
	 */
	protected abstract void onClientMatchListRequest(final ClientMatchListRequestMessage pMessage);
	
	/**
	 * This method should handle an incoming {@link ClientPrepareMatchRequestMessage}.
	 * <p>
	 * Manual:<br>
	 * At first you may check if the {@link #isPlayerSignedIn(UUID)}. Then you
	 * may check if {@link #playerAlreadyPreparingGame(BaseHumanPlayer)}. If 
	 * this is not the case you can proceed with loading an instance of your
	 * implementation of {@link AbsMapOverview} mentioned in the client message.
	 * If the map exists you need to check if the server contains the same 
	 * version of the map. If this is the case you can continue with creating an
	 * instance of your implementation of {@link AbsServerMatchPreparation} and 
	 * register this instance in {@link #mMatchIDsMatchpreparationsMap}. 
	 * Now you can inform the requesting player by sending a
	 * {@link ServerPrepareMatchRequestReplyMessage}.
	 * 
	 * @see #mMatchIDsMatchpreparationsMap
	 * @see #mPlayerIDsPlayerMap
	 * 
	 * @param pMessage - An incoming {@link ClientPrepareMatchRequestMessage}.
	 */
	protected abstract void onClientPrepareMatchRequest(final ClientPrepareMatchRequestMessage pMessage);
	
	/**
	 * This method should handle an incoming {@link ClientMatchJoinLeaveRequestMessage}.
	 * <p>
	 * Manual:<br>
	 * At first you may check if the {@link #isPlayerSignedIn(UUID)}. Then you
	 * may check if a corresponding {@link #matchpreparationExists(UUID)} or
	 * {@link #matchExists(UUID)}. If this is the case you forward the message 
	 * by calling either
	 * {@link AbsServerMatchPreparation#processPlayerJoinOrLeaveReuqest(AbsServerMatchpreparationJoinRequestReplyMessage, BaseServerHumanPlayer, ClientMatchJoinLeaveRequestMessage)
	 * or {@link AbsServerMatch#processClientMatchReconnectRequest(BaseServerHumanPlayer, AbsServerMatchReconnectRequestReplyMessage)}.<br>
	 * Don't forget to send a positive or negative reply to the client.
	 *  
	 * @see #mMatchIDsMatchpreparationsMap
	 * @see #mMatchIDsMatchMap 
	 *  
	 * @param pClientMessage - An incoming {@link ClientMatchJoinLeaveRequestMessage}.
	 */
	protected abstract void onClientMatchJoinOrLeaveRequest(final ClientMatchJoinLeaveRequestMessage pClientMessage);
	
	/**
	 * This method should handle an incoming implementation of 
	 * {@link AbsClientSlotChangeRequestMessage}, {@link ClientStartMatchRequestMessage}
	 * or {@link ClientToggleReadyToPlayMessage}.
	 * <p>
	 * Manual:<br>
	 * At first you may check if the {@link #isPlayerSignedIn(UUID)}. Then you
	 * may check if a corresponding {@link #matchpreparationExists(UUID)}. Now
	 * you may check the type ({@link AbsClientSlotChangeRequestMessage},
	 * {@link ClientToggleReadyToPlayMessage} or {@link ClientStartMatchRequestMessage})
	 * and forward the message object to a corresponding <code>"process"</code>
	 * method.<br>
	 * Don't forget to send a positive or negative reply to the client.
	 * 
	 * @see #mMatchIDsMatchpreparationsMap
	 * 
	 * @param pMessage - An incoming implementation of {@link AbsClientSlotChangeRequestMessage},
	 * {@link ClientStartMatchRequestMessage} or {@link ClientToggleReadyToPlayMessage}.
	 */
	protected abstract void onClientMatchpreparationMessage(final AbsClientMatchMessage pMessage);
	
	
	/**
	 * This method should handle an incoming message which contains a completed 
	 * {@link Step} or {@link Turn}.
	 * <p>
	 * Manual:<br>
	 * At first you may check if the {@link #isPlayerSignedIn(UUID)}. Then you 
	 * may check if a corresponding {@link #matchExists(UUID)}. If this is the
	 * case you forward this message to the 
	 * {@link AbsServerMatch#processClientMatchVerificationMessage(AbsClientMatchMessage, ServerMatchIngameVerificationRequestReplyMessage)}
	 * method of this match.
	 * 	  
	 * @param pMessage - An incoming message which contains a completed 
	 * 					 {@link Step} or {@link Turn}.
	 * 
	 */
	protected abstract void onClientMatchInGameVerificationMessage(final AbsClientMatchMessage pMessage);
	
	/**
	 * This method is a hook which enables you to do something after a player 
	 * left the server and after {@link #cleanUpPlayer(BaseServerHumanPlayer)}
	 * has been called.
	 * 	  
	 * @param pPlayer - The player who left the server.
	 */
	protected abstract void onPlayerLeftServer(final BaseHumanPlayer pPlayer);

	/**
	 * This method is a hook which enables you to do something after a player
	 * has successfully joined the server.
	 * 
	 * @param pPlayer - The player who joined the server.
	 */
	protected abstract void onPlayerJoindServer(final BaseHumanPlayer pPlayer);
	
	/**
	 * This method is a hook which enables you to do something after 
	 * {@link #startServer()} was called and before the corresponding socket
	 * server ({@link #mSocketServer} or {@link #mBluetoothSocketServer}) gets
	 * the "start" call.
	 */
	protected abstract void onPreServerStart();
		
	/**
	 * This method is a hook which enables you to do something after the server
	 * was started.
	 */
	protected abstract void onPostServerStart();
	
	/**
	 * This method is a hook which enables you to do something after 
	 * {@link #stopServer()} was called but before the server shuts down.
	 */
	protected abstract void onPreServerTerminate();
	
	/**
	 * This method is a hook which enables you to do something after the server
	 * shuts down.
	 */
	protected abstract void onPostServerTerminate();
		
	/**
	 * This method should return a map of flags -> client message handlers for
	 * your custom {@link ClientMessage}s which should be handled by the server.
	 * <p>
	 * Notice:<br>
	 * You may use {@link AbsClientMessageCopyHandler} instead of 
	 * {@link IClientMessageHandler} to avoid the problem described in
	 * {@link AbsClientMessageCopyHandler}.
	 * 
	 * @return a map of flags -> client message handlers for your custom 
	 * 		   {@link ClientMessage}s which should be handled by the server.
	 * 
	 * 
	 */
	protected abstract Map<Short, IClientMessageHandler> createAdditionalFlagsClientMessageHandlers();

	/**
	 * This method should return a map of flags -> client message classes for 
	 * your custom {@link ClientMessage}s which should be handled by the server.
	 * 
	 * @return a map of flags -> client message classes for your custom 
	 * 		   {@link ClientMessage}s which should be handled by the server.
	 */
	protected abstract Map<Short,Class<? extends IClientMessage>> getAdditionalFlagsClientMessages();
		
	/**
	 * You can use this method to register your custom {@link ServerMessage}s
	 * which you want to send to the clients.
	 * 
	 * @param pServerMessagePool - The server message pool which you should use
	 * 							   to obtain a new s
	 */
	protected abstract void registerAdditionalServerMessages(final MessagePool<IMessage> pServerMessagePool);
	
	/**
	 * This method checks whether or not a player is signed in.
	 * 	 
	 * @param pPlayerID - The ID of the player who should be checked.
	 * 
	 * @return true if the player is logged in, false otherwise.
	 */
	protected boolean isPlayerSignedIn(final UUID pPlayerID){
		
		return this.mPlayerIDsPlayerMap.containsKey(pPlayerID);
	}
	
	/**
	 * This method checks whether or not a match preparation object which 
	 * has the given match ID exits.
	 * 
	 * @param pMatchID
	 * 
	 * @return true if a match preparation object exists, false otherwise.
	 */
	protected boolean matchpreparationExists(final UUID pMatchID){
		
		return this.mMatchIDsMatchpreparationsMap.containsKey(pMatchID);
	}
	
	/**
	 * This method checks whether or not a match object which 
	 * has the given match ID exits.
	 * 
	 * @param pMatchID
	 * 
	 * @return true if a match object exists, false otherwise.
	 */
	protected boolean matchExists(final UUID pMatchID){
		
		return this.mMatchIDsMatchMap.containsKey(pMatchID);
	}
	
	/**
	 * This method checks if the given player is already preparing a game. In 
	 * most cases you don't let players prepare multiple games at the same time. 
	 *  
	 * @param pPlayer - The player which might already preparing a game.
	 * 
	 * @return
	 */
	protected boolean playerAlreadyPreparingGame(final BaseHumanPlayer pPlayer){
						
		for(MatchPreparationClass match : this.mMatchIDsMatchpreparationsMap.values()){
			
			if(match.getMatchCreator().equals(pPlayer)){
				
				return true;
			}
		}
		
		return false;
	}
	
	
	private MessagePool<IMessage> createServerMessagePool() {
		
		final MessagePool<IMessage> messagePool = new MessagePool<IMessage>();
	
		messagePool.registerMessage(FLAG_SERVER_JOIN_OR_LEAVE_SERVER_REPLY, ServerJoinOrLeaveServerReplyMessage.class);
		messagePool.registerMessage(FLAG_SERVER_CHAT_MESSAGE_FORWARD, ServerChatMessageForwardMessage.class);
		messagePool.registerMessage(FLAG_SERVER_MATCHPREPARATION_PROMOTION, ServerMatchpreparationPromotionMessage.class);

		messagePool.registerMessage(FLAG_SERVER_MATCH_PLAYER_JOINED_LEFT_ANNOUNCEMENT, ServerMatchPlayerJoinedLeftAnnouncementMessage.class);
		messagePool.registerMessage(FLAG_SERVER_MATCHPREPARATION_REQUEST_REPLY, ServerPrepareMatchRequestReplyMessage.class);
		messagePool.registerMessage(FLAG_SERVER_MATCHPREPARATION_SLOT_CHANGE_REQUEST_REPLY, ServerMatchpreparationSlotChangeRequestReplyMessage.class);
		messagePool.registerMessage(FLAG_SERVER_MATCHPREPARATION_STATE_CHANGED_ANNOUNCEMENT, ServerMatchpreprataionStateChangedAnnouncement.class);
		messagePool.registerMessage(FLAG_SERVER_MATCHPREPARATION_START_REQUEST_REPLY, ServerMatchpreprataionStartRequestReplyMessage.class);
		
		
		messagePool.registerMessage(FLAG_SERVER_MATCH_STATE_CHANGED_ANNOUNCEMENT, ServerMatchStateChangedAnnouncementMessage.class);
		messagePool.registerMessage(FLAG_SERVER_MATCH_ROUND_ANNOUNCEMENT, ServerMatchRoundAnnouncementMessage.class);
		messagePool.registerMessage(FLAG_SERVER_MATCH_TURN_ANNOUNCEMENT, ServerMatchTurnAnnouncement.class);
		messagePool.registerMessage(FLAG_SERVER_MATCH_STEP_ANNOUNCEMENT, ServerMatchStepAnnouncement.class);
		messagePool.registerMessage(FLAG_SERVER_MATCH_RECONNECT_REQUEST_REPLY, ServerMatchIngameVerificationRequestReplyMessage.class);		
		
		return messagePool;		
	}
	
	private HashMap<Short,Class<? extends IClientMessage>> getFalgsClientMessagesMap() {
		
		final HashMap<Short,Class<? extends IClientMessage>> map = new HashMap<Short,Class<? extends IClientMessage>>();
		
		map.put(FLAG_CLIENT_CHATMESSAGE, ClientChatmessageMessage.class);
		map.put(FLAG_CLIENT_MATCH_JOIN_LEAVE_REQUST, ClientMatchJoinLeaveRequestMessage.class);
		map.put(FLAG_CLIENT_MATCH_LIST_REQUEST, ClientMatchListRequestMessage.class);
		map.put(FLAG_CLIENT_JOIN_OR_LEAVE_SERVER_REQUEST, ClientJoinOrLeaveServerRequestMessage.class);
		map.put(FLAG_CLIENT_PREPARE_MATCH_REQUEST, ClientPrepareMatchRequestMessage.class);
		
		map.put(FLAG_CLIENT_TOGGLE_READY_TO_PLAY_ANNOUNCEMENT, ClientToggleReadyToPlayMessage.class);
		map.put(FLAG_CLIENT_START_MATCH_REQUEST, ClientStartMatchRequestMessage.class);
		map.put(FLAG_CLIENT_SLOT_CHANGE_FACTION_REQUEST, ClientSlotChangeFactionRequestMessage.class);
		map.put(FLAG_CLIENT_SLOT_CHANGE_PLAYER_REQUEST, ClientSlotChangePlayerRequestMessage.class);
		map.put(FLAG_CLIENT_SLOT_CHANGE_TEAM_REQUEST, ClientSlotChangeTeamReuqestMessage.class);
		map.put(FLAG_CLIENT_SLOT_CHANGE_TO_OPEN_OR_CLOSED_REQUEST, ClientSlotChangeToOpenOrClosedRequestMessage.class);
		
		map.put(FLAG_CLIENT_TURN_VERIFICATION_REQUEST, ClientTurnVerificationRequestMessage.class);
		map.put(FLAG_CLIENT_STEP_VERIFICATION_REQUEST, ClientStepVerificationRequestMessage.class);
		
		return map;
	}
	
	private HashMap<Short, IClientMessageHandler> createClientMessagesFlagsHandlerMap() {
		
		final HashMap<Short, IClientMessageHandler> map = new HashMap<Short, IClientMessageHandler>();
		
		map.put(FLAG_CLIENT_TURN_VERIFICATION_REQUEST, new AbsClientMessageCopyHandler<ClientTurnVerificationRequestMessage, Connection>() {

			@Override
			public void onHandleMessageCopy(final ClientConnector<Connection> pClientConnector,  final ClientTurnVerificationRequestMessage pMessage) {
				
				onClientMatchInGameVerificationMessage(pMessage);
			}
		});
		

		map.put(FLAG_CLIENT_STEP_VERIFICATION_REQUEST, new AbsClientMessageCopyHandler<ClientStepVerificationRequestMessage, Connection>() {

			@Override
			public void onHandleMessageCopy(final ClientConnector<Connection> pClientConnector,  final ClientStepVerificationRequestMessage pMessage) {
				
				onClientMatchInGameVerificationMessage(pMessage);
			}
		});
		
		
		map.put(FLAG_CLIENT_CHATMESSAGE, new AbsClientMessageCopyHandler<ClientChatmessageMessage, Connection>() {

			@Override
			public void onHandleMessageCopy(final ClientConnector<Connection> pClientConnector,  final ClientChatmessageMessage pMessage) {
				
				onClientSendChatMessage(pMessage);
			}
		});
				
		map.put(FLAG_CLIENT_MATCH_JOIN_LEAVE_REQUST, new AbsClientMessageCopyHandler<ClientMatchJoinLeaveRequestMessage, Connection>() {

			@Override
			public void onHandleMessageCopy(final ClientConnector<Connection> pClientConnector, final ClientMatchJoinLeaveRequestMessage pMessage) {
				
				onClientMatchJoinOrLeaveRequest(pMessage);
			}
		});
		
		map.put(FLAG_CLIENT_MATCH_LIST_REQUEST, new AbsClientMessageCopyHandler<ClientMatchListRequestMessage, Connection>() {

			@Override
			public void onHandleMessageCopy(final ClientConnector<Connection> pClientConnector, final ClientMatchListRequestMessage pMessage) {
				
				onClientMatchListRequest(pMessage);
			}
		});
		
	
		map.put(FLAG_CLIENT_PREPARE_MATCH_REQUEST, new AbsClientMessageCopyHandler<ClientPrepareMatchRequestMessage, Connection>() {

			@Override
			public void onHandleMessageCopy(final ClientConnector<Connection> pClientConnector, final ClientPrepareMatchRequestMessage pMessage) {
				onClientPrepareMatchRequest(pMessage);
			}
		});
		

		map.put(FLAG_CLIENT_START_MATCH_REQUEST, new AbsClientMessageCopyHandler<ClientStartMatchRequestMessage, Connection>() {

			@Override
			public void onHandleMessageCopy(final ClientConnector<Connection> pClientConnector, final ClientStartMatchRequestMessage pMessage) {
				
				onClientMatchpreparationMessage(pMessage);
			}
		});
				
		map.put(FLAG_CLIENT_TOGGLE_READY_TO_PLAY_ANNOUNCEMENT, new AbsClientMessageCopyHandler<ClientToggleReadyToPlayMessage, Connection>() {

			@Override
			public void onHandleMessageCopy(final ClientConnector<Connection> pClientConnector, final ClientToggleReadyToPlayMessage pMessage) {
				
				onClientMatchpreparationMessage(pMessage);				
			}
		});
				
		map.put(FLAG_CLIENT_JOIN_OR_LEAVE_SERVER_REQUEST, new AbsClientMessageCopyHandler<ClientJoinOrLeaveServerRequestMessage, Connection>() {

			@Override
			public void onHandleMessageCopy(final ClientConnector<Connection> pClientConnector, final ClientJoinOrLeaveServerRequestMessage pMessage) {
				onClientServerJoinOrLeaveRequest(pClientConnector,pMessage);			
			}
		});
				
		map.put(FLAG_CLIENT_SLOT_CHANGE_FACTION_REQUEST, new AbsClientMessageCopyHandler<ClientSlotChangeFactionRequestMessage, Connection>() {

			@Override
			public void onHandleMessageCopy(final ClientConnector<Connection> pClientConnector, final ClientSlotChangeFactionRequestMessage pMessage) {
				onClientMatchpreparationMessage(pMessage);
			}
		});
		
		map.put(FLAG_CLIENT_SLOT_CHANGE_PLAYER_REQUEST, new AbsClientMessageCopyHandler<ClientSlotChangePlayerRequestMessage, Connection>() {

			@Override
			public void onHandleMessageCopy(final ClientConnector<Connection> pClientConnector, final ClientSlotChangePlayerRequestMessage pMessage) {
				onClientMatchpreparationMessage(pMessage);			
			}
		});
		
		map.put(FLAG_CLIENT_SLOT_CHANGE_TEAM_REQUEST, new AbsClientMessageCopyHandler<ClientSlotChangeTeamReuqestMessage, Connection>() {

			@Override
			public void onHandleMessageCopy(final ClientConnector<Connection> pClientConnector, final ClientSlotChangeTeamReuqestMessage pMessage) {
				onClientMatchpreparationMessage(pMessage);
			}
		});
		
		map.put(FLAG_CLIENT_SLOT_CHANGE_TO_OPEN_OR_CLOSED_REQUEST, new AbsClientMessageCopyHandler<ClientSlotChangeToOpenOrClosedRequestMessage, Connection>() {

			@Override
			public void onHandleMessageCopy(final ClientConnector<Connection> pClientConnector, final ClientSlotChangeToOpenOrClosedRequestMessage pMessage) {
				onClientMatchpreparationMessage(pMessage);
			}
		});
		
		return map;
	}

	/**
	 * This method handles incoming {@link ClientChatmessageMessage}s.<br>
	 * At first it checks if the player is logged in. If this is the case it 
	 * builds a {@link ServerChatMessageForwardMessage} and sends this message
	 * to all recipients listed in the client message which are logged in.
	 * <p>
	 * In a later implementation there will be a feature to store chat message
	 * in matches.
	 * 
	 * @param pMessage - The incoming client chat message.
	 */
	protected void onClientSendChatMessage(ClientChatmessageMessage pMessage) {
		
		if(isPlayerSignedIn(pMessage.getPlayerID())){
		
			final ServerChatMessageForwardMessage chatMessage = (ServerChatMessageForwardMessage) this.mServerMessagePool.obtainMessage(FLAG_SERVER_CHAT_MESSAGE_FORWARD);
			
			chatMessage.setMessage(pMessage.getMessage());
			chatMessage.setSendingPlayer(this.mPlayerIDsPlayerMap.get(pMessage.getPlayerID()));
			chatMessage.setTimeStamp(System.currentTimeMillis());
			
			for(UUID recipientId : pMessage.getReceivingPlayerIDs()){
				
				if(this.mPlayerIDsPlayerMap.containsKey(recipientId)){
			
					try {
						this.mPlayerIDsPlayerMap.get(recipientId).getClientConnector().sendServerMessage(chatMessage);
					} catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}					
				}
			}
			
			if(pMessage.getMatchID() != null){
										
				// TODO: save chat message in matches
				//this.getMatchIDsMatchMap().get(pMessage.getMatchID()).
			}
			
			this.mServerMessagePool.recycleMessage(chatMessage);
		}
	}

	/**
	 * This method handles incoming {@link ClientJoinOrLeaveServerRequestMessage}.<br>
	 * At first this method checks if the game server is already running. Then
	 * it checks the client intention. If the client wants to join the server it
	 * checks {@link #isPlayerSignedIn(UUID)}. This should prevent multiple 
	 * joining of the same player. If the player is not signed in, it joins the
	 * player.
	 * <p>
	 * In a later implementation there will be a (re)authentication mechanism
	 * (playerID/password).
	 * 
	 * @param pClientConnector - The {@link ClientConnector} of the client who
	 * 							 wants to join/leave the server.
	 * @param pMessage - The client message.
	 */
	protected void onClientServerJoinOrLeaveRequest(final ClientConnector<Connection> pClientConnector, final ClientJoinOrLeaveServerRequestMessage pMessage) {

		if(this.mGameServerState == GameServerState.RUNNING){
		
			/*
			 *  TODO: implement a simple (re)authentication mechanism
			 *  	  (playerID/password)
			 */
			
			final BaseServerHumanPlayer player = new BaseServerHumanPlayer(pMessage.getPlayerName(), pMessage.getPlayerID(),pClientConnector);
			
			final ServerJoinOrLeaveServerReplyMessage reply = (ServerJoinOrLeaveServerReplyMessage) this.mServerMessagePool.obtainMessage(FLAG_SERVER_JOIN_OR_LEAVE_SERVER_REPLY);
			
			if(pMessage.isJoiningServer()){
							
				if(!isPlayerSignedIn(pMessage.getPlayerID())){
									
					reply.setAccepted(true);
					reply.setReason(Reason.OK);
					
					this.mPlayerIDsPlayerMap.put(player.getPlayerID(), player);
					
					this.onPlayerJoindServer(player);
					
				}else{
					
					reply.setAccepted(false);
					reply.setReason(Reason.ALREADY_JOINED);
				}
				
				try {
					pClientConnector.sendServerMessage(reply);
				} catch (IOException e) {
					e.printStackTrace();
				}finally{
					
					this.mServerMessagePool.recycleMessage(reply);
				}
				
			}else{
				
				reply.setAccepted(true);
				reply.setReason(Reason.GOODBYE);
			
				try {
					pClientConnector.sendServerMessage(reply);
				} catch (IOException e) {
					e.printStackTrace();
				}finally{
					
					this.mServerMessagePool.recycleMessage(reply);
				}
				
				this.cleanUpPlayer(player);
				
				this.onPlayerLeftServer(player);
			}
		}
	}

	/**
	 * This method starts the game server.
	 */
	public void startServer(){
		
		if(this.mGameServerState != GameServerState.DOWN){
			
			throw new IllegalStateException("The game server has to be DOWN to get started. Current state: "+this.mGameServerState.name());
		}
		
		this.onPreServerStart();
		
		if(this.mIsBluetoothServer){
			
			this.mBluetoothSocketServer.start();
			
		}else{
		
			this.mSocketServer.start();
			
			if(this.mSocketDiscoveryServer != null){
				
				this.mSocketDiscoveryServer.start();
			}
		}
		
		this.mGameServerState = GameServerState.RUNNING;
		
		this.onPostServerStart();
	}
		
	/**
	 * This method stops the game server. It kicks all players before it shuts 
	 * down.
	 */
	public void stopServer(){
		
		if(this.mGameServerState != GameServerState.RUNNING){
			
			throw new IllegalStateException("The game server has to be RUNNING to get stopped. Current state: "+this.mGameServerState.name());
		}
		
		this.onPreServerTerminate();
		
		this.mGameServerState = GameServerState.DOWN;
		
		// Kick all players
		for(BaseServerHumanPlayer currentPlayer : this.mPlayerIDsPlayerMap.values()){
			
			/*
			 *  TODO: implement a "goodbye" message which should be send to the
			 *  	  player
			 */
			this.cleanUpPlayer(currentPlayer);
		}
				
		if(this.mIsBluetoothServer){
			
			this.mBluetoothSocketServer.terminate();
			
		}else{
		
			if(this.mSocketDiscoveryServer != null){
				
				this.mSocketDiscoveryServer.terminate();
			}
			
			this.mSocketServer.terminate();
		}
		
		this.onPostServerTerminate();
	}


	/**
	 * @return the mGameServerState
	 */
	public GameServerState getGameServerState() {
		return mGameServerState;
	}
	

	private void onConnected(final ClientConnector pClientConnector) {
						
		for(Short typeFlag : this.mFlagsClientMessageHandlersMap.keySet()){
			
			pClientConnector.registerClientMessage(typeFlag, this.mFlagsClientMessageClassesMap.get(typeFlag), this.mFlagsClientMessageHandlersMap.get(typeFlag));
		}
	}
	
	private void onDisconnected(final ClientConnector pClientConnector) {
				
		final Iterator<BaseServerHumanPlayer> playerIt = this.mPlayerIDsPlayerMap.values().iterator();
		
		// Resolve Player
		while(playerIt.hasNext()){
			
			final BaseServerHumanPlayer currentPlayer = playerIt.next();
			
			// If player is found
			if(currentPlayer.getClientConnector().equals(pClientConnector)){

				this.cleanUpPlayer(currentPlayer);
								
				this.onPlayerLeftServer(currentPlayer);
				
				break;
			}
		}
	}
	
	/**
	 * This method removes matches in preparation mode managed by the player, 
	 * leaves the player from joined games and removes and the player himself.
	 * 
	 * @see #mMatchIDsMatchMap
	 * @see #mMatchIDsMatchpreparationsMap
	 * @see #mPlayerIDsPlayerMap
	 * 
	 * @param pPlayer - The player who should be removed.
	 */
	private void cleanUpPlayer(final BaseServerHumanPlayer pPlayer){
	
		for(MatchPreparationClass currentMatch : this.mMatchIDsMatchpreparationsMap.values()){
	
			if(currentMatch.getMatchCreator().equals(pPlayer)){
				
				currentMatch.cancelMatch();
			
			}else if(currentMatch.getJoinedPlayers().contains(pPlayer)){
				
				currentMatch.removePlayer(pPlayer);
			}
		}
			
		this.mPlayerIDsPlayerMap.remove(pPlayer.getPlayerID());
	}
		
	private class BluetoothSocketConnectionClientConnectorListener implements IBluetoothSocketConnectionClientConnectorListener{

		@Override
		public void onStarted(
				ClientConnector<BluetoothSocketConnection> pClientConnector) {
			
			AbsGameServer.this.onConnected(pClientConnector);
		}

		@Override
		public void onTerminated(
				ClientConnector<BluetoothSocketConnection> pClientConnector) {

			AbsGameServer.this.onDisconnected(pClientConnector);
		}
	}
	
	private class SocketConnectionClienConnectorListener implements ISocketConnectionClientConnectorListener{

		@Override
		public void onStarted(ClientConnector<SocketConnection> pClientConnector) {
			
			AbsGameServer.this.onConnected(pClientConnector);
		}

		@Override
		public void onTerminated(
				ClientConnector<SocketConnection> pClientConnector) {
			
			AbsGameServer.this.onDisconnected(pClientConnector);
		}
	}

	/**
	 * @return the mServerMessagePool
	 */
	public MessagePool<IMessage> getServerMessagePool() {
		return mServerMessagePool;
	}

	/**
	 * @return the mMatchIDsMatchesMap
	 */
	public Map<UUID, MatchPreparationClass> getMatchIDsMatchPreparationsMap() {
		return mMatchIDsMatchpreparationsMap;
	}

	/**
	 * @return the mMatchIDsMatchMap
	 */
	public Map<UUID, ServerMatchClass> getMatchIDsMatchMap() {
		return mMatchIDsMatchMap;
	}
}
